package com.fast.alden.common.service.impl;

import com.fast.alden.common.exception.BizException;
import com.fast.alden.common.exception.BizExceptionEnum;
import com.fast.alden.common.service.BaseEntityService;
import com.fast.alden.data.model.BaseEntity;
import com.fast.alden.data.repo.BaseEntityRepository;
import jakarta.persistence.Transient;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import jakarta.transaction.Transactional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public abstract class BaseEntityServiceImpl<E extends BaseEntity, R extends BaseEntityRepository<E>> implements BaseEntityService<E, R> {
    protected abstract Class<E> getEClass();

    /**
     * 获取实体仓库接口
     */
    protected abstract R getRepository();

    @Override
    @Transactional
    public E create(E entity) {
        if (entity == null) {
            throw new BizException(BizExceptionEnum.ENTITY_IS_NULL);
        }
        return getRepository().save(entity);
    }

    @Override
    @Transactional
    public Iterable<E> createAll(Iterable<E> entities) {
        return getRepository().saveAll(entities);
    }

    @Override
    public List<E> list() {
        return getRepository().findAll();
    }

    @Override
    public List<E> list(List<Long> ids) {
        return getRepository().findAllById(ids);
    }

    @Override
    public Page<E> page(Pageable pageable) {
        return getRepository().findAll(pageable);
    }

    @Override
    public E retrieve(Long id) {
        return getRepository().findById(id).orElse(null);
    }

    @Override
    public E retrieveWithException(Long id) {
        return getRepository().findById(id).orElseThrow(() -> new BizException(BizExceptionEnum.ENTITY_DATA_DOSE_NOT_EXIST, id));
    }

    @Override
    @Transactional
    public E update(E entity) {
        if (entity == null) {
            throw new BizException(BizExceptionEnum.ENTITY_IS_NULL);
        }
        if (entity.getId() == null) {
            throw new BizException(BizExceptionEnum.ENTITY_ID_IS_NULL);
        }
        this.retrieveWithException(entity.getId());
        return getRepository().save(entity);
    }

    @Override
    @Transactional
    public Iterable<E> updateAll(Iterable<E> entities) {
        return getRepository().saveAll(entities);
    }

    @Override
    @Transactional
    public List<E> remove(List<Long> ids) {
        List<E> list = getRepository().findAllById(ids);
        // 查询到的数据
        List<Long> successIds = list.stream().map(BaseEntity::getId).toList();
        // 未查询到的数据
        List<Long> failureIds = ids.stream().filter(id -> !successIds.contains(id)).toList();

        if (!failureIds.isEmpty()) {
            throw new BizException(BizExceptionEnum.PARTIAL_ENTITIES_DATA_DOSE_NOT_EXIST);
        }

        getRepository().deleteAll(list);
        return list;
    }

    @Override
    public Page<E> search(String searchText, Pageable pageable) {
        Specification<E> specification = (root, query, criteriaBuilder) -> {
            try {
                return buildFuzzyPredicateList(searchText, root, query, criteriaBuilder);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        };

        return search(specification, pageable);
    }

    @Override
    public Page<E> search(E searchVo, Pageable pageable) {
        Specification<E> specification = (root, query, criteriaBuilder) -> {
            try {
                return buildFuzzyPredicateList(searchVo, root, query, criteriaBuilder);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        };

        return search(specification, pageable);
    }

    @Override
    public Page<E> search(Specification<E> specification, Pageable pageable) {
        return getRepository().findAll(specification, pageable);
    }

    @Override
    public List<E> search(String searchText) {
        Specification<E> specification = (root, query, criteriaBuilder) -> {
            try {
                return buildFuzzyPredicateList(searchText, root, query, criteriaBuilder);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        };

        return search(specification);
    }

    @Override
    public List<E> search(E searchVo) {
        Specification<E> specification = (root, query, criteriaBuilder) -> {
            try {
                return buildFuzzyPredicateList(searchVo, root, query, criteriaBuilder);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        };

        return search(specification);
    }

    @Override
    public List<E> search(Specification<E> specification) {
        return getRepository().findAll(specification);
    }

    /**
     * 获取本类及其父类的字段属性
     *
     * @param clazz 当前类对象
     * @return 字段数组
     */
    private List<Field> getAllFields(Class<?> clazz) {
        List<Field> fieldList = new ArrayList<>();
        while (clazz != null) {
            fieldList.addAll(Arrays.asList(clazz.getDeclaredFields()));
            clazz = clazz.getSuperclass();
        }
        return fieldList;
    }

    /**
     * 构造模糊查询谓词列表
     *
     * @param searchText      查询文本
     * @param root
     * @param query
     * @param criteriaBuilder
     * @return
     * @throws IllegalAccessException
     */
    protected Predicate buildFuzzyPredicateList(String searchText, Root<E> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) throws IllegalAccessException {
        List<Predicate> predicateList = new ArrayList<>();
        if (StringUtils.hasText(searchText)) {
            Class<E> clazz = getEClass();
            List<Field> fields = getAllFields(clazz);
            for (Field f : fields) {
                // 跳过非数据库字段属性
                if (f.isAnnotationPresent(Transient.class)) {
                    continue;
                }
                Predicate predicate = null;
                if (f.getType().equals(String.class)) {
                    // String类型谓词使用模糊查询
                    predicate = criteriaBuilder.like(root.get(f.getName()).as(String.class), "%" + searchText + "%");
                } else {
                    // 其他类型谓词使用精确查询
                    predicate = criteriaBuilder.equal(root.get(f.getName()).as(String.class), searchText);
                }
                predicateList.add(predicate);
            }
        }
        return criteriaBuilder.or(predicateList.toArray(new Predicate[predicateList.size()]));
    }

    /**
     * 构造模糊查询谓词列表
     *
     * @param searchVo        查询对象
     * @param root
     * @param query
     * @param criteriaBuilder
     * @return
     * @throws IllegalAccessException
     */
    protected Predicate buildFuzzyPredicateList(E searchVo, Root<E> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) throws IllegalAccessException {
        List<Predicate> predicateList = new ArrayList<>();
        if (searchVo != null) {
            Class<E> clazz = getEClass();
            List<Field> fields = getAllFields(clazz);
            for (Field f : fields) {
                f.setAccessible(true);
                // 跳过非数据库字段属性
                if (f.isAnnotationPresent(Transient.class)) {
                    continue;
                }
                // 跳过参数为null的字段属性
                if (f.get(searchVo) == null) {
                    continue;
                }
                Predicate predicate = null;
                if (f.getType().equals(String.class)) {
                    // String类型谓词使用模糊查询
                    predicate = criteriaBuilder.like(root.get(f.getName()).as(String.class), "%" + f.get(searchVo) + "%");
                } else if (f.getType().isEnum()) {
                    // 枚举类型谓词，实体类中全部使用@Enumerated(EnumType.STRING)确保数据库中保存的是枚举的name
                    predicate = criteriaBuilder.equal(root.get(f.getName()).as(String.class), ((Enum<?>) f.get(searchVo)).name());
                } else if (f.getType().equals(boolean.class) || f.getType().equals(Boolean.class)) {
                    // boolean类型谓词使用精确查询
                    predicate = criteriaBuilder.equal(root.get(f.getName()).as(Boolean.class), f.get(searchVo));
                } else if (f.getType().equals(byte.class) || f.getType().equals(Byte.class)) {
                    // byte类型谓词使用精确查询
                    predicate = criteriaBuilder.equal(root.get(f.getName()).as(Byte.class), f.get(searchVo));
                } else if (f.getType().equals(short.class) || f.getType().equals(Short.class)) {
                    // short类型谓词使用精确查询
                    predicate = criteriaBuilder.equal(root.get(f.getName()).as(Short.class), f.get(searchVo));
                } else if (f.getType().equals(int.class) || f.getType().equals(Integer.class)) {
                    // int类型谓词使用精确查询
                    predicate = criteriaBuilder.equal(root.get(f.getName()).as(Integer.class), f.get(searchVo));
                } else if (f.getType().equals(long.class) || f.getType().equals(Long.class)) {
                    // long类型谓词使用精确查询
                    predicate = criteriaBuilder.equal(root.get(f.getName()).as(Long.class), f.get(searchVo));
                } else {
                    // 其他类型谓词使用精确查询
                    predicate = criteriaBuilder.equal(root.get(f.getName()).as(String.class), f.get(searchVo));
                }
                predicateList.add(predicate);
            }
        }

        return criteriaBuilder.and(predicateList.toArray(new Predicate[predicateList.size()]));
    }
}
